/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once
#include <baja/common.hpp>
#include <journal/views/interfaces.h>
#include <baja/tv_interpolation.hpp>

namespace journal { namespace view {

namespace b = baja;

struct ianimation_observer : public b::ibase
{
    BAJA_INTERFACE_NOTAG(ianimation_observer);

    virtual void onUpdate(const int id) = 0;
    virtual void onEnd(const int id) = 0;
};

class animation : public std::enable_shared_from_this<animation>
{
public:
    animation(const int id) : _id(id), _finished(true) {}

    animation(
        const int id,
        const b::float32 start,
        const b::float32 duration,
        const b::float32 startValue,
        const b::float32 endValue,
        const b::float32 p1 = 0.2f,
        const b::float32 p2 = 0.2f) : _id(id)
    {
        this->set(start, duration, startValue, endValue, p1, p2);
    }

    const b::float32 current() { return _current; }

    bool isFinished() { return _finished; }
    void cancel() { this->finish(); }
    const b::tv_interpolation& interpolation() { return _ip; }
    b::float32 duration()
    {
        return _ip.t3() - _ip.t0();
    }
    b::float32 start()
    {
        return _ip.t0();
    }
    b::float32 end()
    {
        return _ip.t3();
    }

    void finish()
    { 
        _finished = true; 
        if (_observer) _observer->onEnd(_id);
    }

    void apply()
    {
        if (_finished) return;

        b::float32 t = constants::general::currentTime();

        _current = _ip.apply(t);

        if (_observer) _observer->onUpdate(_id);

        if (t >= _ip.t3())
        {
            this->finish();
        }
    }

    void setObserver(const std::shared_ptr<ianimation_observer>& observer) { _observer = observer; } 

    void set(
        const b::float32 start,
        const b::float32 duration,
        const b::float32 startValue,
        const b::float32 endValue,
        const b::float32 p1 = 0.2f,
        const b::float32 p2 = 0.2f
        )
    {
        _ip.set(start, start+duration, startValue, endValue, p1, p2);
        _finished = false;
        _current = startValue;
    }

private:
    int _id;
    std::shared_ptr<ianimation_observer> _observer;
    bool _finished;
    b::float32 _current;
    b::tv_interpolation _ip;
};

class animation_2d : public std::enable_shared_from_this<animation_2d>
{
public:
    animation_2d(const int id) : _id(id), _finished(true)
    {}

    animation_2d(
        const int id,
        const b::float32 start,
        const b::float32 duration,
        const m::point2d& startValue,
        const m::point2d& endValue,
        const b::float32 p1 = 0.2f,
        const b::float32 p2 = 0.2f) : _id(id)
    {
        this->set(start, duration, startValue, endValue, p1, p2);
    }

    const m::point2d current() { return _current; }
    bool isFinished() { return _finished; }
    void cancel() { this->finish(); }
    const b::tv_interpolation_2d<m::point2d>& interpolation() { return _ip; }
    b::float32 duration()
    {
        return _ip.t3() - _ip.t0();
    }
    b::float32 start()
    {
        return _ip.t0();
    }
    b::float32 end()
    {
        return _ip.t3();
    }

    void finish()
    { 
        _finished = true; 
        if (_observer) _observer->onEnd(_id);
    }

    void apply()
    {
        if (_finished) return;

        b::float32 t = constants::general::currentTime();

        _current = _ip.apply(t);

        if (_observer) _observer->onUpdate(_id);

        if (t >= _ip.t3())
        {
            this->finish();
        }
    }

    void setObserver(const std::shared_ptr<ianimation_observer>& observer) { _observer = observer; } 

    void set(
        const b::float32 start,
        const b::float32 duration,
        const m::point2d& startValue,
        const m::point2d& endValue,
        const b::float32 p1 = 0.2f,
        const b::float32 p2 = 0.2f
        )
    {
        _ip.set(start, start+duration, startValue, endValue, p1, p2);
        _finished = false;
        _current = startValue;
    }

private:
    int _id;
    std::shared_ptr<ianimation_observer> _observer;
    bool _finished;
    m::point2d _current;
    b::tv_interpolation_2d<m::point2d> _ip;
    b::float32 _progress;
};

class animation_3d : public std::enable_shared_from_this<animation_2d>
{
public:
    animation_3d(const int id) : _id(id), _finished(true)
    {}

    animation_3d(
        const int id,
        const b::float32 start,
        const b::float32 duration,
        const m::point3d& startValue,
        const m::point3d& endValue,
        const b::float32 p1 = 0.2f,
        const b::float32 p2 = 0.2f) : _id(id)
    {
        this->set(start, duration, startValue, endValue, p1, p2);
    }

    const m::point3d current() { return _current; }
    bool isFinished() { return _finished; }
    void cancel() { this->finish(); }
    const b::tv_interpolation_3d<m::point3d>& interpolation() { return _ip; }
    b::float32 duration()
    {
        return _ip.t3() - _ip.t0();
    }
    b::float32 start()
    {
        return _ip.t0();
    }
    b::float32 end()
    {
        return _ip.t3();
    }

    void finish()
    { 
        _finished = true; 
        if (_observer) _observer->onEnd(_id);
    }

    void apply()
    {
        if (_finished) return;

        b::float32 t = constants::general::currentTime();

        _current = _ip.apply(t);

        if (_observer) _observer->onUpdate(_id);

        if (t >= _ip.t3())
        {
            this->finish();
        }
    }

    void setObserver(const std::shared_ptr<ianimation_observer>& observer) { _observer = observer; } 

    void set(
        const b::float32 start,
        const b::float32 duration,
        const m::point3d& startValue,
        const m::point3d& endValue,
        const b::float32 p1 = 0.2f,
        const b::float32 p2 = 0.2f
        )
    {
        _ip.set(start, start+duration, startValue, endValue, p1, p2);
        _finished = false;
        _current = startValue;
    }

private:
    int _id;
    std::shared_ptr<ianimation_observer> _observer;
    bool _finished;
    m::point3d _current;
    b::tv_interpolation_3d<m::point3d> _ip;
};

}} // namespace journal::view